const std = @import("std");
const setting = @import("../setting.zig");
const Cell = @import("../cell.zig");

const IndexBlock = @import("index_block.zig");
const Map = @import("../map.zig");

const Index = @This();

allocator: std.mem.Allocator,
list: std.ArrayList(*IndexBlock),
rows: usize,
cols: usize,

pub fn init(map: *Map) *@This() {
    const cols: usize = map.width / setting.MaxCellScanRange + if (map.width % setting.MaxCellScanRange == 0) 0 else @as(usize, 1);
    const rows: usize = map.height / setting.MaxCellScanRange + if (map.height % setting.MaxCellScanRange == 0) 0 else @as(usize, 1);

    const item = map.allocator.create(@This()) catch @panic("Out of memory");
    item.* = .{
        .allocator = map.allocator,
        .list = std.ArrayList(*IndexBlock).init(map.allocator),
        .rows = rows,
        .cols = cols,
    };

    item.setup();

    return item;
}

pub fn deinit(self: *@This()) void {
    for (self.list.items) |item| {
        item.deinit();
    }
    self.list.deinit();

    self.allocator.destroy(self);
}

pub fn index(self: *@This(), comptime T: type, item: *T, initial: bool) void {
    const _x: f32 = std.math.floor(item.x) / setting.MaxCellScanRange;
    const _y: f32 = std.math.floor(item.y) / setting.MaxCellScanRange;

    const col_idx: usize = @intFromFloat(std.math.floor(_x));
    const row_idx: usize = @intFromFloat(std.math.floor(_y));

    const idx: usize = row_idx * self.cols + col_idx;
    if (idx < self.list.items.len) {
        const block = self.list.items[idx];

        if (initial) {
            item.index_block = block;
            block.add(T, item);
        } else {
            if (item.index_block != block) {
                item.index_block.remove(T, item);

                item.index_block = block;
                block.add(T, item);
            }
        }
    } else {
        std.debug.print("Error Index : {d} of {s} @ {d} {d} {d}\n", .{ item.id, @typeName(T), item.x, item.y, idx });
    }
}

fn setup(self: *@This()) void {
    for (0..self.cols * self.rows) |_| {
        self.list.append(IndexBlock.init(self)) catch {};
    }

    for (0..self.list.items.len) |idx| {
        var item = self.list.items[idx];

        var has_up = false;
        if (idx >= self.cols) {
            has_up = true;
            item.up = self.list.items[idx - self.cols];
        } else {
            has_up = false;
        }

        const has_right = if (idx % self.cols == self.cols - 1) false else true;
        const right_idx: usize = idx + 1;
        if (has_right) {
            item.right = self.list.items[right_idx];
        }

        const has_left = if (idx % self.cols == 0) false else true;
        if (has_left) {
            item.left = self.list.items[idx - 1];
        }

        var has_down = false;
        const down_idx = idx + self.cols;
        if (down_idx < self.list.items.len) {
            has_down = true;
            item.down = self.list.items[down_idx];
        } else {
            has_down = false;
        }

        if (has_up) {
            if (has_right) {
                item.right_up = self.list.items[right_idx - self.cols];
            }

            if (has_left) {
                item.left_up = self.list.items[idx - 1 - self.cols];
            }
        }

        if (has_down) {
            if (has_right) {
                item.right_down = self.list.items[right_idx + self.cols];
            }

            if (has_left) {
                item.left_down = self.list.items[idx - 1 + self.cols];
            }
        }
    }
}
